package com.leyou.search.service.impl;

import com.leyou.common.dto.PageDTO;
import com.leyou.common.exceptions.LyException;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.SearchParamDTO;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.SearchService;
import com.leyou.starter.elastic.dto.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import static com.leyou.search.constants.SearchConstants.INDEX_SOURCE;
import static com.leyou.search.constants.SearchConstants.SEARCH_FIELD;
import static com.leyou.search.constants.SearchConstants.SUGGESTION_FIELD;

@Service
@Slf4j
public class SerachServiceImpl implements SearchService {

    @Autowired
    private GoodsRepository repository;

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private RestHighLevelClient client;

    @Override
    public void createIndexAndMapping() {
        //删除存在的索引库，try，防止没有
        try {
            repository.deleteIndex();
        } catch (Exception e) {
            log.info("索引库存在，需要重新创建");
        }

        //创建新的索引库
        repository.createIndex(INDEX_SOURCE);
        log.info("创建索引库成功！");
    }

    @Override
    public void loadData() {
        log.info("准备开始导入数据！");
        //初始化页码和行数
        int page = 1,rows = 20;
        //死循环
        while (true) {
            log.info("开始查询第{}页数据",page);
            //获取数据
            PageDTO<SpuDTO> pageDTO = itemClient.querySpuByPage(page, rows, true, null, null, null);
            //获取spuDto集合
            List<SpuDTO> spuDTOList = pageDTO.getItems();
            //遍历集合，转换为Goods对象
            List<Goods> list = spuDTOList.stream().map(this::buildGoods).collect(Collectors.toList());
            //添加数据
            repository.saveAll(list);
            log.info("第{}页数据成功导入索引库",page);
            //页码增加，翻页
            page++;
            //判断页码大小，++后大于最大页码，就跳出死循环
            if (page > pageDTO.getTotalPage()) {
                System.out.println("page = " + page);
                log.info("全部数据导入完成，共{}页，{}条",page-1,pageDTO.getTotal());
                break;
            }
        }

    }

    @Override
    public Mono<List<String>> getSuggestion(String key) {
        Mono<List<String>> mono = repository.suggestBySingleField(SUGGESTION_FIELD, key);
        return mono;
    }

    @Override
    public Mono<PageInfo<Goods>> searchGoods(SearchParamDTO searchParamDTO) {
        //创建一个查询条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //调用方法，获取关键字查询
        MatchQueryBuilder matchQueryBuilder = getMatchQueryBuilder(searchParamDTO);
        //添加关键字查询
        sourceBuilder.query(matchQueryBuilder);
        //获取排序条件
        Boolean desc = searchParamDTO.getDesc();
        String sortBy = searchParamDTO.getSortBy();
        //判断sortBy是否存在，综合时sortBy为空
        if (StringUtils.isNotBlank(sortBy)) {
            //排序条件不为空
            //判断desc的值，true表示倒序，fales表示正序
            sourceBuilder.sort(sortBy, desc ? SortOrder.DESC : SortOrder.ASC);
        }
        //获取分页条件，from表示(page - 1) * getSize()
        int size = searchParamDTO.getSize();
        Integer from = searchParamDTO.getFrom();
        //添加分页条件
        sourceBuilder.from(from).size(size);
        //添加高亮条件
        sourceBuilder.highlighter(new HighlightBuilder().field(SEARCH_FIELD).preTags("<am>").postTags("</am>"));
        //过滤不需要的参数  要的写前面的数组中，不要的放后面
        sourceBuilder.fetchSource(new String[]{"id", "title", "sold", "prices","image"}, null);
        //搜索
        Mono<PageInfo<Goods>> mono = repository.queryBySourceBuilderForPageHighlight(sourceBuilder);
        return mono;
    }

    //抽取的获取关键字查询的方法
    private MatchQueryBuilder getMatchQueryBuilder(SearchParamDTO searchParamDTO) {
        //获取关键字
        String key = searchParamDTO.getKey();
        //判断key是否存在
        if (StringUtils.isBlank(key)) {
            //条件不能不存在
            throw new LyException(400, "查询条件不存在！");
        }
        return QueryBuilders.matchQuery(SEARCH_FIELD, key);
    }

    @Override
    public Mono<Map<String, List<?>>> queryFilterList(SearchParamDTO searchParamDTO) {
        //创建一个查询条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //调用方法，获取关键字查询条件
        MatchQueryBuilder matchQueryBuilder = getMatchQueryBuilder(searchParamDTO);
        //添加关键字查询
        sourceBuilder.query(matchQueryBuilder).size(0);
        //聚合  分类
        sourceBuilder.aggregation(AggregationBuilders.terms("categoryAgg").field("categoryId").size(20));
        //聚合，品牌
        sourceBuilder.aggregation(AggregationBuilders.terms("brandAgg").field("brandId").size(20));
        //聚合，分类
        sourceBuilder.aggregation(AggregationBuilders.nested("specAgg","specs")
                .subAggregation(AggregationBuilders.terms("specNameAgg").field("specs.name").size(20)
                .subAggregation(AggregationBuilders.terms("specValueAgg").field("specs.value").size(20))));
        //获取一个请求对象
        SearchRequest searchRequest = new SearchRequest();
        //添加请求参数
        searchRequest.source(sourceBuilder);
        //发出请求
        return Mono.create(monoSink -> {
            client.searchAsync(searchRequest, RequestOptions.DEFAULT, new ActionListener<SearchResponse>() {
                @Override
                public void onResponse(SearchResponse response) {
                    //调用方法解析聚合结果
                    Map<String, List<?>> filterMap = handleAggResponse(response);
                    //成功，发射
                    monoSink.success(filterMap);
                }

                @Override
                public void onFailure(Exception e) {
                    //失败，报错
                    monoSink.error(e);
                }
            });
        });
    }

    @Override
    public void saveById(Long spuId) {
        //根据id查询spu数据
        SpuDTO spuDTO = itemClient.queryGoodsById(spuId);
        //转换为GOods对象
        Goods goods = buildGoods(spuDTO);
        //添加数据
        repository.save(goods);
    }

    @Override
    public void deleteById(Long spuId) {
        //根据id删除
        repository.deleteById(spuId);
    }

    private Map<String, List<?>> handleAggResponse(SearchResponse response) {
        //解析,创建一个新的map集合，需要有序，用linked
        Map<String, List<?>> filterMap = new LinkedHashMap<>();
        //解析分类的结果
        //根据名称获取分类的聚合结果
        Aggregations aggregations = response.getAggregations();
        Terms categoryAgg = aggregations.get("categoryAgg");
        //判断是否为空
        if (categoryAgg == null) {
            return filterMap;
        }
        //获取bucket
        List<? extends Terms.Bucket> categoryAggBuckets = categoryAgg.getBuckets();
        //bucket的key就是分类id
        List<Long> categoryIds = categoryAggBuckets.stream().map(Terms.Bucket::getKeyAsNumber).map(Number::longValue).collect(Collectors.toList());
        //查询获取cate集合
        List<CategoryDTO> categoryDTOList = itemClient.queryCategoryByIds(categoryIds);
        //将分类集合存入map中
        filterMap.put("分类", categoryDTOList);
        //解析品牌，操作同分类
        //根据名称获取分类的聚合结果
        Terms brandAgg = aggregations.get("brandAgg");
        //判断是否为空
        if (brandAgg == null) {
            return filterMap;
        }
        //获取bucket
        List<? extends Terms.Bucket> brandAggBuckets = brandAgg.getBuckets();
        //bucket的key就是分类id
        List<Long> brandIds = brandAggBuckets.stream().map(Terms.Bucket::getKeyAsNumber).map(Number::longValue).collect(Collectors.toList());
        //查询获取cate集合
        List<BrandDTO> brandDTOList = itemClient.queryBrandByIds(brandIds);
        //将分类集合存入map中
        filterMap.put("品牌", brandDTOList);
        //解析规格参数
        //根据名称获取分类的聚合结果
        Nested specAgg = aggregations.get("specAgg");
        //获取name的聚合
        Terms nameAgg = specAgg.getAggregations().get("specNameAgg");
        //判断是否为空
        if (nameAgg == null) {
            return filterMap;
        }
        //获取name聚合的buckets
        List<? extends Terms.Bucket> nameAggBuckets = nameAgg.getBuckets();
        //遍历buckets
        for (Terms.Bucket nameAggBucket : nameAggBuckets) {
            //key就是规格参数的名字
            String name = nameAggBucket.getKeyAsString();
            //获取该key下的聚合结果
            Terms valueAgg = nameAggBucket.getAggregations().get("specValueAgg");
            //获取buckets
            List<? extends Terms.Bucket> valueAggBuckets = valueAgg.getBuckets();
            //获取valueAggieBuckets中所有key值的集合
            List<String> values = valueAggBuckets.stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            //添加到map中
            filterMap.put(name, values);
        }
        return filterMap;
    }

    private Goods buildGoods(SpuDTO spuDTO) {
        //1.title,拼接标题，品牌名，分类名
        String title = spuDTO.getTitle().concat(spuDTO.getBrandName()).concat(spuDTO.getCategoryName());
        //2.自动补全字段，商品名，品牌名，分类名
        //防止重复，用set集合
        Set<String> suggestion = new HashSet<>();
        //添加商品名，品牌名
        suggestion.add(spuDTO.getName());
        suggestion.add(spuDTO.getBrandName());
        //获取分类名,按‘/’裁剪
        String[] names = spuDTO.getCategoryName().split("/");
        //分类名数组添加到集合中
        Collections.addAll(suggestion, names);
        //3.sku相关
        //获取sku集合
        List<SkuDTO> skus = spuDTO.getSkus();
        //获取spu的id
        Long spuDTOId = spuDTO.getId();
        //判断是否获取到数据
        if (skus == null) {
            skus = itemClient.querySkuBySpuId(spuDTOId);
        }
        //获取第一个sku的第一章图片
        String image = StringUtils.substringBefore(skus.get(0).getImages(),",");
        //初始化一个销量和，价格集合
        Long sold = 0L;
        Set<Long> prices = new HashSet<>();
        //遍历集合
        for (SkuDTO skuDTO : skus) {
            //计算销量和
            sold += skuDTO.getSold();
            //想价格集合中添加价格
            prices.add(skuDTO.getPrice());
        }
        //4.规格参数的键值对集合
        //获取键值对集合
        List<SpecParamDTO> specParamDTOS = itemClient.querySpecsValues(spuDTOId, true);
        //新建一个List集合，泛型是map集合
        List<Map<String, Object>> specs = new ArrayList<>(specParamDTOS.size());
        //遍历集合
        for (SpecParamDTO specParamDTO : specParamDTOS) {
            //新建一个map集合
            Map<String, Object> map = new HashMap<>();
            //向map添加数据,name和value
            map.put("name", specParamDTO.getName());
            //不能直接放value，调用方法获取区间
            map.put("value", chooseSegment(specParamDTO));
            //向specs集合中添加map
            specs.add(map);
        }
        //创建一个新的Goods
        Goods goods = new Goods();
        goods.setUpdateTime(new Date());
        goods.setTitle(title);
        goods.setSuggestion(suggestion);  // todo 自动补全字段
        goods.setSpecs(specs);       //todo 规格参数的键值对
        goods.setSold(sold);        //todo 销量
        goods.setPrices(prices);      //todo 价格，各sku价格不同，价格几何
        goods.setImage(image);       //todo 各sku图片不同，任意一张
        goods.setId(spuDTO.getId());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setBrandId(spuDTO.getBrandId());

        return goods;
    }

    private Object chooseSegment(SpecParamDTO p) {
        Object value = p.getValue();
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        if(!p.getNumeric() || StringUtils.isBlank(p.getSegments()) || value instanceof Collection){
            return value;
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }
}
